//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Argument Parser open source project
//
// Copyright (c) 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

import ArgumentParser
import ArgumentParserToolInfo

extension CommandInfoV0 {
  var doccReferenceFileName: String {
    doccReferenceTitle + ".md"
  }

  var doccReferenceDocumentTitle: String {
    let parts = (superCommands ?? []) + [commandName]
    return parts.joined(separator: ".").uppercased()
  }

  var doccReferenceTitle: String {
    let parts = (superCommands ?? []) + [commandName]
    return parts.joined(separator: ".")
  }

  var doccReferenceName: String {
    let parts = (superCommands ?? []) + [commandName]
    return parts.joined(separator: " ")
  }
}

extension CommandInfoV0 {
  /// Recursively parses a command to generate markdown content that describes the command.
  /// - Parameters:
  ///   - path: The path of subcommands from the root command.
  ///   - markdownStyle: The flavor of markdown to emit, either `docc` or `github`
  /// - Returns: A multi-line markdown file that describes the command.
  ///
  /// If `path` is empty, it represents a top-level command.
  /// Otherwise it's a subcommand, potentially recursive to multiple levels.
  func toMarkdown(_ path: [String], markdownStyle: OutputStyle) -> String {
    var result =
      String(repeating: "#", count: path.count + 1)
      + " \(self.doccReferenceTitle)\n\n"

    // sets the max width for generating code blocks of content based
    // on the style
    let blockWrapLength: Int =
      switch markdownStyle {
      case .docc: 60
      case .github: 80
      }

    if path.count == 0 {
      result += "<!-- Generated by swift-argument-parser -->\n\n"
    }

    if let abstract = self.abstract {
      result += "\(abstract)\n\n"
    }

    if let args = self.arguments, args.count != 0 {
      result += "```\n"
      let commandString = (path + [self.commandName]).joined(separator: " ")
      result +=
        commandString
        + self.usage(
          startlength: commandString.count, wraplength: blockWrapLength)
      result += "\n```\n\n"
    }

    if let discussion = self.discussion {
      result += "\(discussion)\n\n"
    }

    if let args = self.arguments {
      for arg in args {
        guard arg.shouldDisplay else {
          continue
        }

        switch markdownStyle {
        case .docc:
          result += "- term **\(arg.identity())**:\n\n"
        case .github:
          result += "**\(arg.identity()):**\n\n"
        }

        if let abstract = arg.abstract {
          result += "*\(abstract)*\n\n"
        }
        if let discussion = arg.discussion {
          result += discussion + "\n\n"
        }
        result += "\n"
      }
    }

    for subcommand in self.subcommands ?? [] {
      result +=
        subcommand.toMarkdown(
          path + [self.commandName], markdownStyle: markdownStyle) + "\n\n"
    }

    return result
  }

  /// Returns a mutl-line string that presents the arguments for a command.
  /// - Parameters:
  ///   - startlength: The starting width of the line this multi-line string appends onto.
  ///   - wraplength: The maximum width  of the multi-linecode block.
  /// - Returns: A wrapped, multi-line string that wraps the commands arguments into a text block.
  public func usage(startlength: Int, wraplength: Int) -> String {
    guard let args = self.arguments else {
      return ""
    }

    var multilineString = ""
    // This is a greedy algorithm to wrap the arguments into a
    // multi-line string that is expected to be returned within
    // a markdown code block (pre-formatted text).
    var currentLength = startlength
    for arg in args where arg.shouldDisplay {
      let nextUsage = arg.usage()
      if currentLength + arg.usage().count > wraplength {
        // the next usage() string exceeds the max width, wrap it.
        multilineString.append("\n  \(nextUsage)")
        currentLength = nextUsage.count + 2  // prepend spacing length of 2
      } else {
        // the next usage() string doesn't exceed the max width
        multilineString.append(" \(nextUsage)")
        currentLength += nextUsage.count + 1
      }
    }
    return multilineString
  }
}

extension ArgumentInfoV0 {
  /// Returns a string that describes the use of the argument.
  ///
  /// If `shouldDisplay` is `false`, an empty string is returned.
  public func usage() -> String {
    guard self.shouldDisplay else {
      return ""
    }

    let names: [String]

    if let myNames = self.names {
      names = myNames.filter { $0.kind == .long }.map(\.name)
    } else if let preferred = self.preferredName {
      names = [preferred.name]
    } else if let value = self.valueName {
      names = [value]
    } else {
      return ""
    }

    // TODO: default values, short, etc.

    var inner: String
    switch self.kind {
    case .positional:
      inner = "<\(names.joined(separator: "|"))>"
    case .option:
      inner = "--\(names.joined(separator: "|"))=<\(self.valueName ?? "")>"
    case .flag:
      inner = "--\(names.joined(separator: "|"))"
    }

    if self.isRepeating {
      inner += "..."
    }

    if self.isOptional {
      return "[\(inner)]"
    }

    return inner
  }

  public func identity() -> String {
    let names: [String]
    if let myNames = self.names {
      names = myNames.filter { $0.kind == .long }.map(\.name)
    } else if let preferred = self.preferredName {
      names = [preferred.name]
    } else if let value = self.valueName {
      names = [value]
    } else {
      return ""
    }

    // TODO: default values, values, short, etc.

    let inner: String
    switch self.kind {
    case .positional:
      inner = "\(names.joined(separator: "|"))"
    case .option:
      inner = "--\(names.joined(separator: "|"))=\\<\(self.valueName ?? "")\\>"
    case .flag:
      inner = "--\(names.joined(separator: "|"))"
    }
    return inner
  }
}
